package com.test.fenbushi.utils;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.util.StringUtils;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisLockUtils {

    /**
     * lua脚本，判断锁住值是否为当前线程持有，是的话解锁，不是的话解锁失败
     *
     */
    private static final String DISTRIBUTE_LOCK_SCRIPT_UNLOCK_VAL = "if"+
            " redis.call('get', KEYS[1]) == ARGV[1]" +
            " then" +
            " return redis.call('del', KEYS[1])" +
            " else" +
            " return 0" +
            " end";


    private static final String DELIMITER = "|";
    /**
     * 可以通过注入的方式分配
     */
    private static final ScheduledExecutorService EXECUTOR_SERVICE =new ScheduledThreadPoolExecutor(10);
    private final StringRedisTemplate stringRedisTemplate;
    public RedisLockUtils(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 获取锁（存在死锁风险）
     * @param lockKey
     * @param value
     * @param time   超时时间
     * @param unit   过期单位
     * @return
     */
    public boolean tryLock(final String lockKey,final String value, final long time,final TimeUnit unit){
        return stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> connection.set(lockKey.getBytes(),value.getBytes(), Expiration.from(time,unit), RedisStringCommands.SetOption.SET_IF_ABSENT));
    }

    /**
     * 获取锁
     * @param lockKey
     * @param uuid
     * @param timeout 超时时间
     * @param unit     过期单位
     * @return
     */
    public boolean lock(String lockKey,final String uuid,long timeout,final TimeUnit unit){
        final long milliseconds = Expiration.from(timeout,unit).getExpirationTimeInMilliseconds();
        boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,(System.currentTimeMillis()+milliseconds)+DELIMITER+uuid);
        if(success){
            stringRedisTemplate.expire(lockKey,timeout,TimeUnit.SECONDS);
        }else{
            String oldVal = stringRedisTemplate.opsForValue().getAndSet(lockKey,(System.currentTimeMillis()+milliseconds)+DELIMITER+uuid);
            final String[] oldValues = oldVal.split(Pattern.quote(DELIMITER));
            if(Long.parseLong(oldValues[0])+1 <= System.currentTimeMillis()){
                return true;
            }
        }
        return success;
    }

    public void unlock(String lockKey,String value){
        unlock(lockKey,value,0,TimeUnit.MILLISECONDS);
    }

    /**
     * 延迟unlock
     * @param lockKey
     * @param uuid
     * @param delayTime
     * @param unit
     */
    public void unlock(final String lockKey,final String uuid, long delayTime,TimeUnit unit){
        if(StringUtils.isEmpty(lockKey)){
            return;
        }
        if(delayTime <= 0){
            doUnlock(lockKey,uuid);
        }else{
            EXECUTOR_SERVICE.schedule(() -> doUnlock(lockKey,uuid),delayTime,unit);
        }
    }

    /**
     *
     * @param lockKey
     * @param uuid
     */
    private void doUnlock(final String lockKey,final String uuid){
        String val = stringRedisTemplate.opsForValue().get(lockKey);
        final String[] values = val.split(Pattern.quote(DELIMITER));
        if(values.length<=0){
            return;
        }
        if(uuid.equals(values[1])){
            stringRedisTemplate.delete(lockKey);
        }
    }
}
